package com.sunday.authorization.security.configuration;

import com.sunday.authorization.security.access.JsonAuthenticationFailureHandler;
import com.sunday.authorization.security.authentication.CustomUsernamePasswordAuthenticationFilter;
import com.sunday.authorization.security.constants.SecurityConstants;
import com.sunday.redis.lua.core.RedisLuaTemplate;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractGenericHttpMessageConverter;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
import org.springframework.security.authorization.ObservationAuthorizationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ErrorAuthenticationFailureHandler;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.filter.CompositeFilter;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.List;
import java.util.function.Supplier;

/**
 * 调用链
 *
 * @see CompositeFilter#doFilter(ServletRequest, ServletResponse, FilterChain)
 * <p>
 * 全量Filter
 * @see HttpSecurityBuilder#addFilter(Filter) 内部包含所有得Filter，请根据具体业务进行查看
 * <p>
 * 判定 AuthorizationFilter 授权过滤器是否成功
 * @see AuthorizationFilter#doFilter(ServletRequest, ServletResponse, FilterChain)
 * @see ObservationAuthorizationManager#check(Supplier, Object)
 * @see RequestMatcherDelegatingAuthorizationManager#check(Supplier, HttpServletRequest)
 * @see AuthenticatedAuthorizationManager#check(Supplier, Object)
 * @see AuthenticatedAuthorizationManager.AuthenticatedAuthorizationStrategy#isGranted(Authentication)
 * <p>
 * 当授权没有成功的时候，异常被谁拿走了?
 * <p>
 * try {
 * AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, request);
 * this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, request, decision);
 * if (decision != null && !decision.isGranted()) {
 * throw new AccessDeniedException("Access Denied");
 * }
 * chain.doFilter(request, response);        * 		}
 * finally {
 * request.removeAttribute(alreadyFilteredAttributeName);
 * }
 * @see AuthorizationFilter#doFilter(ServletRequest, ServletResponse, FilterChain) 当AuthorizationFilter报出异常后由调用者链路接收到异常
 * @see ExceptionTranslationFilter#doFilter(HttpServletRequest, HttpServletResponse, FilterChain) 开始处理 throw new AccessDeniedException("Access Denied");
 * @see ExceptionTranslationFilter#handleSpringSecurityException(HttpServletRequest, HttpServletResponse, FilterChain, RuntimeException) 异常分解处理
 * @see Http403ForbiddenEntryPoint#commence(HttpServletRequest, HttpServletResponse, AuthenticationException)
 * <p>
 * <p>
 * <p>
 * https://docs.spring.io/spring-authorization-server/reference/getting-started.html
 * <p>
 * 这些路径是OAuth 2.0和OpenID Connect (OIDC)中授权服务器的各个端点（endpoint），每个端点都有其特定的用途和功能：
 * 1. OIDC客户端注册端点 (OIDC Client Registration Endpoint)
 * 路径: /connect/register
 * 作用: 允许客户端动态地注册到授权服务器中。它是OpenID Connect的一个扩展。
 * 2. 授权端点 (Authorization Endpoint)
 * 路径: /oauth2/authorize
 * 作用: 用户交互的核心，用户在这里登录并授权客户端代表他们行动。
 * 3. 令牌端点 (Token Endpoint)
 * 路径: /oauth2/token
 * 作用: 客户端用来交换授权码、刷新令牌或使用客户端凭证进行认证以获取访问令牌。
 * 4. OIDC登出端点 (OIDC Logout Endpoint)
 * 路径: /connect/logout
 * 作用: 提供了一个登出用户的方式，在OpenID Connect会话管理规范中定义。
 * 5. 设备验证端点 (Device Verification Endpoint)
 * 路径: /oauth2/device_verification
 * 作用: 在设备流授权中用于轮询授权请求的状态，不是OAuth 2.0的标准部分，可能是特定实现。
 * 6. 设备授权端点 (Device Authorization Endpoint)
 * 路径: /oauth2/device_authorization
 * 作用: 在设备授权流程中用来提交授权请求，允许在没有浏览器的设备上进行用户认证和授权。
 * 7. JSON Web Key Set (JWKS) 端点
 * 路径: /oauth2/jwks
 * 作用: 发布用于签名令牌的公钥信息，客户端可以用来验证令牌的完整性。
 * 8. 令牌撤销端点 (Token Revocation Endpoint)
 * 路径: /oauth2/revoke
 * 作用: 可以撤销令牌，让访问令牌或刷新令牌失效。
 * 9. OIDC用户信息端点 (OIDC User Info Endpoint)
 * 路径: /userinfo
 * 作用: 在OpenID Connect协议中，客户端可以请求这个端点以获取关于用户的信息。
 * 10. 令牌检视端点 (Token Introspection Endpoint)
 * 路径: /oauth2/introspect
 * 作用: 其他服务器可以查询当前访问令牌的状态，如是否有效，过期时间等等。
 */
@Slf4j
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    static final List<String> excludedAuthPages = List.of(
            /** 登录 */
            SecurityConstants.LOGIN_PATH_URL,
            /** 图片 获取验证码 url */
            SecurityConstants.VERIFY_CODE_PATH_URL,
            /** 手机 获取验证码 url */
            SecurityConstants.CAPTCHA_PATH_URL
    );

    /**
     * 协议端点的 Spring Security 过滤器链。
     *
     * @param http
     * @return {@link SecurityFilterChain}
     */
    @Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {

        /**
         * @see OAuth2AuthorizationServerConfigurer#createConfigurers()
         */
        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
                new OAuth2AuthorizationServerConfigurer();

        authorizationServerConfigurer
                // 设置确认页面
                .authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint.consentPage("/oauth2/consent"))
                .oidc(Customizer.withDefaults());
        RequestMatcher endpointsMatcher = authorizationServerConfigurer
                .getEndpointsMatcher();

//        // 将默认的OAuth2端点匹配器和你的自定义匹配器合并到一个新的RequestMatcher
//        RequestMatcher requestMatcher = new OrRequestMatcher(
//                endpointsMatcher,
//                // 建你自己的RequestMatcher, 自定义的请求匹配逻辑
//                request -> excludedAuthPages.stream().anyMatch(url -> url.equals(request.getRequestURI()))
//        );

        http
                .securityMatcher(endpointsMatcher)
                .authorizeHttpRequests((authorize) -> authorize
//                                .requestMatchers(excludedAuthPages.toArray(String[]::new)).permitAll()
                                .anyRequest().authenticated()
                )
                .csrf((csrf) -> csrf.ignoringRequestMatchers(endpointsMatcher))
                .with(authorizationServerConfigurer, Customizer.withDefaults())
        ;

//        // 添加 CORS 配置到授权服务器过滤器链
//        http.cors(cors -> cors.configurationSource(_ -> {
//
//            CorsConfiguration configuration = new CorsConfiguration();
//            // 允许所有来源的请求
//            configuration.setAllowedOrigins(Arrays.asList("*"));
//            // 允许的请求方法
//            configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
//            // 允许的请求头
//            configuration.setAllowedHeaders(Arrays.asList("*"));
//            // 是否允许携带凭证（如 cookies）
////            configuration.setAllowCredentials(true);
//
//            return configuration;
//        }));

        /**
         * 异常处理
         * @see OAuth2TokenEndpointFilter#doFilterInternal(HttpServletRequest, HttpServletResponse, FilterChain)
         * @see OAuth2ErrorAuthenticationFailureHandler#onAuthenticationFailure(HttpServletRequest, HttpServletResponse, AuthenticationException)
         * @see AbstractHttpMessageConverter#write(Object, MediaType, HttpOutputMessage)
         * @see OAuth2ErrorHttpMessageConverter#writeInternal(OAuth2Error, HttpOutputMessage)
         * @see AbstractGenericHttpMessageConverter#write(Object, Type, MediaType, HttpOutputMessage)
         *
         * authenticationConverter.convert(request); 处理流程
         * 0 = {OAuth2AuthorizationCodeAuthenticationConverter@17872}
         * 1 = {OAuth2RefreshTokenAuthenticationConverter@17873}
         * 2 = {OAuth2ClientCredentialsAuthenticationConverter@17874}
         * 3 = {OAuth2DeviceCodeAuthenticationConverter@17875}
         * @see
         */

        /**
         * 官方页面配置
         */
//        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
//        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
//                .oidc(Customizer.withDefaults());    // Enable OpenID Connect 1.0
        http
                // Redirect to the login page when not authenticated from the
                // authorization endpoint
                .exceptionHandling((exceptions) -> exceptions
                        .defaultAuthenticationEntryPointFor(
                                new LoginUrlAuthenticationEntryPoint("/login"),
                                new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
                        )
                )
//                // Accept access tokens for User Info and/or Client Registration
//                .oauth2ResourceServer((resourceServer) -> resourceServer
//                        .jwt(Customizer.withDefaults()))
                // 跨站访问
                .cors(Customizer.withDefaults())
        ;

//        http.csrf(csrf -> csrf.disable());

        return http.build();
    }

    /**
     * 用于身份验证的 Spring Security 过滤器链。
     *
     * @param http
     * @return {@link SecurityFilterChain}
     */
    @Bean
    @Order(2)
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, RedisLuaTemplate redisLuaTemplate) throws Exception {

        /**
         * @see HttpSecurity#authorizeHttpRequests(Customizer)
         * @see AuthorizeHttpRequestsConfigurer#configure(HttpSecurityBuilder)
         * @see ObservationAuthorizationManager#check(Supplier, Object)
         * @see RequestMatcherDelegatingAuthorizationManager#check(Supplier, HttpServletRequest)
         */
        http
                .authorizeHttpRequests(authorize -> authorize
//                        .anyRequest().permitAll()
                                .requestMatchers(excludedAuthPages.toArray(String[]::new)).permitAll()
                                .requestMatchers(HttpMethod.GET,
//                                        "/login",
                                        "/oauth2/consent",
                                        // 操作页面资源
                                        "/bootstrap/**",
                                        "/images/**",
                                        "/js/**",
                                        "/favicon.ico",
                                        "/error",
                                        // 接口调试入口
                                        "/webjars/**",
                                        "/v3/**",
                                        "/doc.html"
                                )
                                .permitAll()

                )
                // Form login handles the redirect to the login page from the
                // authorization server filter chain
//                .formLogin(Customizer.withDefaults())
//                .exceptionHandling(exceptionHandling -> exceptionHandling
//                        .accessDeniedHandler(new JsonServerAccessDeniedHandler())
////                        .authenticationEntryPoint(new JsonAuthenticationEntryPoint())
//                )
                /**
                 * @see FormLoginConfigurer#FormLoginConfigurer()
                 */
                .formLogin(formLogin -> {
                            formLogin
                                    .loginPage("/login")
                                    .failureHandler(new JsonAuthenticationFailureHandler());
                            /**
                             * 替换掉原有的 UsernamePasswordAuthenticationFilter
                             * @see AbstractAuthenticationFilterConfigurer#setAuthenticationFilter(AbstractAuthenticationProcessingFilter)
                             */
//                            Method method = ReflectionUtils.findMethod(FormLoginConfigurer.class, "setAuthenticationFilter", AbstractAuthenticationProcessingFilter.class);
//                            method.setAccessible(true);
//                            ReflectionUtils.invokeMethod(method, formLogin, new CustomUsernamePasswordAuthenticationFilter(redisLuaTemplate));
                        }
                )
                // 跨站访问
                .cors(Customizer.withDefaults())
//                .addFilterAt(customUsernamePasswordAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
//                .exceptionHandling(exceptionHandling -> exceptionHandling
//                        /**
//                         * 异常处理
//                         * @see ExceptionTranslationFilter#handleSpringSecurityException(HttpServletRequest, HttpServletResponse, FilterChain, RuntimeException) 异常分解处理
//                         * @see Http403ForbiddenEntryPoint#commence(HttpServletRequest, HttpServletResponse, AuthenticationException)
//                         */
//                        .authenticationEntryPoint(new JsonAuthenticationEntryPoint())
//                )
        ;


//        // 添加 CORS 配置到授权服务器过滤器链
//        http.cors(cors -> cors.configurationSource(_ -> {
//
//            CorsConfiguration configuration = new CorsConfiguration();
//            // 允许所有来源的请求
//            configuration.setAllowedOrigins(Arrays.asList("*"));
//            // 允许的请求方法
//            configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
//            // 允许的请求头
//            configuration.setAllowedHeaders(Arrays.asList("*"));
//            // 是否允许携带凭证（如 cookies）
////            configuration.setAllowCredentials(true);
//
//            return configuration;
//        }));

//        http
//                .authorizeHttpRequests(authorize -> authorize
//                        .requestMatchers("/webjars/**", "/v3/**", "/favicon.ico", "/doc.html").permitAll()
//                        .anyRequest().authenticated()
//                )
//                .exceptionHandling(exceptionHandling -> exceptionHandling
//                        /**
//                         * 异常处理
//                         * @see ExceptionTranslationFilter#handleSpringSecurityException(HttpServletRequest, HttpServletResponse, FilterChain, RuntimeException) 异常分解处理
//                         * @see Http403ForbiddenEntryPoint#commence(HttpServletRequest, HttpServletResponse, AuthenticationException)
//                         */
//                        .authenticationEntryPoint(new JsonAuthenticationEntryPoint())
//                )
//        ;

//        http.csrf(csrf -> csrf.disable());

        return http.build();
    }

}


/**
 * @see org.springframework.security.authentication.dao.DaoAuthenticationProvider#additionalAuthenticationChecks(UserDetails, UsernamePasswordAuthenticationToken)
 * <p>
 * messages_zh_CN.properties
 * <p>
 * AbstractAccessDecisionManager.accessDenied：不允许访问
 * AbstractLdapAuthenticationProvider.emptyPassword：用户名或密码错误
 * AbstractSecurityInterceptor.authenticationNotFound：未在SecurityContext中找到认证对象
 * AbstractUserDetailsAuthenticationProvider.badCredentials：用户名或密码错误
 * AbstractUserDetailsAuthenticationProvider.credentialsExpired：用户凭证已过期
 * AbstractUserDetailsAuthenticationProvider.disabled：用户已失效
 * AbstractUserDetailsAuthenticationProvider.expired：用户账号已过期
 * AbstractUserDetailsAuthenticationProvider.locked：用户账号已被锁定
 * AbstractUserDetailsAuthenticationProvider.onlySupports：仅仅支持UsernamePasswordAuthenticationToken
 * AccountStatusUserDetailsChecker.credentialsExpired：用户凭证已过期
 * AccountStatusUserDetailsChecker.disabled：用户已失效
 * AccountStatusUserDetailsChecker.expired：用户账号已过期
 * AccountStatusUserDetailsChecker.locked：用户账号已被锁定
 * AclEntryAfterInvocationProvider.noPermission：给定的Authentication对象({0})根本无权操控领域对象({1})
 * AnonymousAuthenticationProvider.incorrectKey：展示的AnonymousAuthenticationToken不含有预期的key
 * BindAuthenticator.badCredentials：用户名或密码错误
 * BindAuthenticator.emptyPassword：用户名或密码错误
 * CasAuthenticationProvider.incorrectKey：展示的CasAuthenticationToken不含有预期的key
 * CasAuthenticationProvider.noServiceTicket：未能正确提供待验证的CAS服务票根
 * ConcurrentSessionControlAuthenticationStrategy.exceededAllowed：已经超过了当前主体({0})被允许的最大会话数量
 * DigestAuthenticationFilter.incorrectRealm：响应结果中的Realm名字({0})同系统指定的Realm名字({1})不吻合
 * DigestAuthenticationFilter.incorrectResponse：错误的响应结果
 * DigestAuthenticationFilter.missingAuth：遗漏了针对'auth' QOP的、必须给定的摘要取值; 接收到的头信息为{0}
 * DigestAuthenticationFilter.missingMandatory：遗漏了必须给定的摘要取值; 接收到的头信息为{0}
 * DigestAuthenticationFilter.nonceCompromised：Nonce令牌已经存在问题了，{0}
 * DigestAuthenticationFilter.nonceEncoding：Nonce未经Base64编码; 相应的nonce取值为 {0}
 * DigestAuthenticationFilter.nonceExpired：Nonce已经过期/超时
 * DigestAuthenticationFilter.nonceNotNumeric：Nonce令牌的第1部分应该是数字，但结果却是{0}
 * DigestAuthenticationFilter.nonceNotTwoTokens：Nonce应该由两部分取值构成，但结果却是{0}
 * DigestAuthenticationFilter.usernameNotFound：用户名{0}未找到
 * ExceptionTranslationFilter.insufficientAuthentication：访问此资源需要完全身份验证
 * JdbcDaoImpl.noAuthority：没有为用户{0}指定角色
 * JdbcDaoImpl.notFound：未找到用户{0}
 * LdapAuthenticationProvider.badCredentials：用户名或密码错误
 * LdapAuthenticationProvider.credentialsExpired：用户凭证已过期
 * LdapAuthenticationProvider.disabled：用户已失效
 * LdapAuthenticationProvider.expired：用户账号已过期
 * LdapAuthenticationProvider.locked：用户账号已被锁定
 * LdapAuthenticationProvider.emptyUsername：用户名不允许为空
 * LdapAuthenticationProvider.onlySupports：仅仅支持UsernamePasswordAuthenticationToken
 * PasswordComparisonAuthenticator.badCredentials：用户名或密码错误
 * ProviderManager.providerNotFound：未查找到针对{0}的AuthenticationProvider
 * RememberMeAuthenticationProvider.incorrectKey：展示RememberMeAuthenticationToken不含有预期的key
 * RunAsImplAuthenticationProvider.incorrectKey：展示的RunAsUserToken不含有预期的key
 * SubjectDnX509PrincipalExtractor.noMatching：未在subjectDN: {0}中找到匹配的模式
 * SwitchUserFilter.noCurrentUser：不存在当前用户
 * SwitchUserFilter.noOriginalAuthentication：不能够查找到原先的已认证对象
 */